﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Flower.Client;
using Flower.Directory.Util;
using Flower.Services;
using Flower.Services.Data;
using Flower.Workflow;
using Process = Flower.Services.Data.Process;

namespace Flower.Testing
{
    public class DirectoryBuilder
    {
        private readonly IDirectory _directory;
        private readonly Stack<string> _parents = new Stack<string>();

        public DirectoryBuilder(IDirectory directory)
        {
            _directory = directory;
        }

        public DirectoryBuilder Root(string path)
        {
            DirectoryEntry root = _directory.Get(path);

            if (root == null)
            {
                throw new ArgumentException("The entry with the specified path doesn't exist.", "path");
            }

            _parents.Push(root.Id);

            return this;
        }

        public DirectoryBuilder Assembly(string name, byte[] image, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Assembly 
                    {
                        Name = name,
                        Image = image,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Folder(string name, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Folder
                    {
                        Name = name,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Link(string name, string targetId, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Link
                    {
                        Name = name,
                        Target = targetId,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Message<T>(string name, BlobFormat format, T message, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Message
                    {
                        Name = name,
                        Data = message.ToBlob(format),
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Process(string workflow, Func<AclBuilder, AclBuilder> acl = null)
        {
            string pid;
            return Process(workflow, out pid, acl);
        }

        public DirectoryBuilder Process(string workflow, out string pid, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                pid = _directory.Save
                (
                    _parents.Peek(),
                    new Process
                    {
                        Workflow = workflow,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Processor(string name, string configuration, Func<AclBuilder, AclBuilder> acl = null)
        {
            return Processor
            (
                name,
                (configuration != null) ? 
                BlobUtil.WriteBlob(BlobFormat.TextXml, writer => 
                {
                    var xml = new XmlDocument();
                    xml.LoadXml(configuration);

                    xml.WriteTo(writer);
                }) : 
                null, 
                acl
            );
        }

        public DirectoryBuilder Processor(string name, Blob configuration, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Processor
                    {
                        Name = name,
                        Configuration = configuration,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder RegisterProcessor(string name)
        {
            Root("/Processors")
                .Processor(name, (string)null)
                    .Folder("Pending").End()
                    .Folder("Running").End()
                    .Folder("Waiting").End()
                    .Folder("Finished").End()
                    .Folder("Broken").End()
                    .Folder("Suspended").End()
                .End()
            .End();

            _directory.Save("/Processors/Active", new Link { Name = name, Target = _directory.GetInfo("/Processors/" + name).Id });

            return this;
        }

        public DirectoryBuilder Role(string name, IEnumerable<string> users, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Role
                    {
                        Name = name,
                        Users = users.ToList(),
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;

        }

        public DirectoryBuilder ServiceContainer(string name, string configuration, Func<AclBuilder, AclBuilder> acl = null)
        {
            var xml = new XmlDocument();
            xml.LoadXml(configuration);

            return ServiceContainer(name, BlobUtil.WriteBlob(BlobFormat.TextXml, xml.WriteTo), acl);
        }

        public DirectoryBuilder ServiceContainer(string name, Blob configuration, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new ServiceContainer
                    {
                        Name = name,
                        Configuration = configuration,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Set(string name, Type messageType, int capacity, Func<AclBuilder, AclBuilder> acl = null)
        {
            return Set(name, messageType.AssemblyQualifiedName, capacity, acl);
        }

        public DirectoryBuilder Set(string name, string messageType, int capacity, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Set
                    {
                        Name = name,
                        MessageType = messageType,
                        Capacity = capacity,
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder State(Workflow.Process state, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new State
                    {
                        Data = state.ToBlob(BlobFormat.GZipXml, true),
                        AccessControlList =
                            (acl != null) ? ((AclBuilder)acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder Workflow(string name, Type definitionType, IEnumerable<string> preferredProcessors, Func<AclBuilder, AclBuilder> acl = null)
        {
            return Workflow(name, definitionType.AssemblyQualifiedName, preferredProcessors, acl);
        }

        public DirectoryBuilder Workflow(string name, string definitionType, IEnumerable<string> preferredProcessors, Func<AclBuilder, AclBuilder> acl = null)
        {
            _parents.Push
            (
                _directory.Save
                (
                    _parents.Peek(),
                    new Services.Data.Workflow
                    {
                        Name = name,
                        DefinitionType = definitionType,
                        PreferredProcessors = (preferredProcessors != null) ? preferredProcessors.ToList() : null,
                        AccessControlList =
                            (acl != null) ? (acl(new AclBuilder())).Acl : new List<AccessControlEntry>()
                    }
                )
            );
            return this;
        }

        public DirectoryBuilder End()
        {
            _parents.Pop();
            return this;
        }

        public IDirectory Build()
        {
            return _directory;
        }
    }
}
